package xyz.scootaloo.kami.cloud.verticle

import io.vertx.core.Handler
import io.vertx.core.http.HttpMethod
import io.vertx.core.http.HttpServer
import io.vertx.ext.web.Router
import io.vertx.ext.web.RoutingContext
import io.vertx.ext.web.handler.BodyHandler
import io.vertx.ext.web.handler.CorsHandler
import io.vertx.kotlin.coroutines.CoroutineVerticle
import io.vertx.kotlin.coroutines.await
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import xyz.scootaloo.kami.cloud.ext.webdav.WebDavSupport
import xyz.scootaloo.kami.cloud.handler.AuthenticateMode
import xyz.scootaloo.kami.cloud.handler.RequestHandlerRegister
import xyz.scootaloo.kami.cloud.handler.httpRequestRegisterCollects
import xyz.scootaloo.kami.cloud.handler.pre.DigestEncryptHandler
import xyz.scootaloo.kami.cloud.handler.queryMillisSeconds
import xyz.scootaloo.kami.cloud.lang.*
import xyz.scootaloo.kami.cloud.util.RequestReporter

/**
 * @author flutterdash@qq.com
 * @since 2022/4/10 14:00
 */
@EventLoop(Constant.CTX_SERVER)
object HttpServerVerticle : CoroutineVerticle() {

    private val log by lazy { getLogger("http-server") }

    private var port: Int = 0
    private lateinit var server: HttpServer

    override suspend fun start() {
        val conf = Global.APP_SER_CONF
        port = conf.port

        server = vertx.createHttpServer()

        server.requestHandler(handlers())

        server.listen(port).await()
        log.info("httpserver start succeed, listen port:$port")
    }

    override suspend fun stop() {
        server.close().await()
    }

    fun deploymentId(): String {
        return deploymentID
    }

    @Public
    fun launchCoroutine(block: suspend CoroutineScope.() -> Unit) = launch(block = block)

    private fun handlers(): Router {
        val config = Global.APP_SER_CONF
        val rootRouter = Router.router(vertx)

        rootRouter.route().handler(preProcessHandler())

        rootRouter.route().handler(requestBodyHandler())

        if (config.enableCorsHandler) {
            rootRouter.route("/*").handler(corsHandler())
        }

        if (config.enableWebDAV) {
            bindRouter(rootRouter, WebDavSupport)
        }

        for (register in httpRequestRegisterCollects) {
            bindRouter(rootRouter, register)
        }
        return rootRouter
    }

    private fun bindRouter(rootRouter: Router, register: RequestHandlerRegister) {
        val subRouter = Router.router(vertx)
        when (register.authMode) {
            AuthenticateMode.DIGEST -> {
                DigestEncryptHandler.handler(subRouter)
            }
            AuthenticateMode.JWT -> {
                TODO("JWT未实现")
            }
            AuthenticateMode.NONE -> {
            }
        }

        register.register(subRouter)
        rootRouter.mountSubRouter(register.mountPoint, subRouter)
    }

    private fun requestBodyHandler(): BodyHandler {
        return BodyHandler.create()
            .setMergeFormAttributes(true)
            .setHandleFileUploads(true)
            .setDeleteUploadedFilesOnEnd(true)
    }

    private fun corsHandler(): CorsHandler {
        val corsAllowedHeaders = setOf(
            "Content-Type", "Accept", "Access-Control-Allow-Origin",
            "origin", "x-requested-with", "Authorization"
        )

        val corsAllowedMethods = setOf(
            HttpMethod.GET, HttpMethod.POST, HttpMethod.OPTIONS,
            HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.PUT,
            HttpMethod.PATCH
        )

        return CorsHandler.create("*")
            .maxAgeSeconds(600)
            .allowedHeaders(corsAllowedHeaders)
            .allowedMethods(corsAllowedMethods)
    }

    private fun preProcessHandler(): Handler<RoutingContext> {
        val serverConfig = Global.APP_SER_CONF
        val logConfig = Global.APP_LOG_CONF
        return Handler { ctx ->
            if (serverConfig.enableSlowQueryLog || logConfig.enableHttpRecord) {
                if (serverConfig.enableSlowQueryLog) {
                    ctx.put(Constant.KEY_QUERY_BEGIN, currentTimeMillis())
                }

                ctx.addEndHandler {
                    blocking {
                        if (logConfig.logHttpRequestDetailWhenErrorOccurs && isErrorOccurs(ctx)) {
                            showQueryDetails(ctx, true)
                            return@blocking
                        }
                        if (serverConfig.enableSlowQueryLog && isSlowQuery(ctx)) {
                            showQueryDetails(ctx)
                            return@blocking
                        }
                        if (logConfig.enableHttpRecord) {
                            processHttpRecord(ctx)
                        }
                    }.submit()
                }
            }
            ctx.next()
        }
    }

    private fun isErrorOccurs(ctx: RoutingContext): Boolean {
        return ctx.get<Any>(Constant.KEY_HTTP_PROC_ERR) != null
    }

    private fun isSlowQuery(ctx: RoutingContext): Boolean {
        return ctx.queryMillisSeconds() > Global.APP_SER_CONF.slowQueryLimit
    }

    private fun showQueryDetails(ctx: RoutingContext, warn: Boolean = false) {
        val details = RequestReporter.fullReport(ctx)
        if (warn) log.warn(details)
        else log.info(details)
    }

    private fun processHttpRecord(ctx: RoutingContext) {
        log.info(RequestReporter.simpleRequestReport(ctx))
    }
}